/*
*  Copyright (c) 2001 Sun Microsystems, Inc.  All rights
*  reserved.
*
*  Redistribution and use in source and binary forms, with or without
*  modification, are permitted provided that the following conditions
*  are met:
*
*  1. Redistributions of source code must retain the above copyright
*  notice, this list of conditions and the following disclaimer.
*
*  2. Redistributions in binary form must reproduce the above copyright
*  notice, this list of conditions and the following disclaimer in
*  the documentation and/or other materials provided with the
*  distribution.t
*
*  3. The end-user documentation included with the redistribution,
*  if any, must include the following acknowledgment:
*  "This product includes software developed by the
*  Sun Microsystems, Inc. for Progroupject JXTA."
*  Alternately, this acknowledgment may appear in the software itself,
*  if and wherever such third-party acknowledgments normally appear.
*
*  4. The names "Sun", "Sun Microsystems, Inc.", "JXTA" and "Project JXTA"
*  must not be used to endorse or promote products derived from this
*  software without prior written permission. For written
*  permission, please contact Project JXTA at http://www.jxta.org.
*
*  5. Products derived from this software may not be called "JXTA",
*  nor may "JXTA" appear in their name, without prior written
*  permission of Sun.
*
*  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
*  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
*  OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
*  DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
*  ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
*  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
*  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
*  USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
*  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
*  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
*  OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
*  SUCH DAMAGE.
*  ====================================================================
*
*  This software consists of voluntary contributions made by many
*  individuals on behalf of Project JXTA.  For more
*  information on Project JXTA, please see
*  <http://www.jxta.org/>.
*
*  This license is based on the BSD license adopted by the Apache Foundation.
*
*  $Id: SearchPanel.java,v 1.14 2006/06/06 20:04:08 nano Exp $
*/
package net.jxta.myjxta.ui;

import net.jxta.myjxta.MyJXTA;
import net.jxta.myjxta.View;
import net.jxta.myjxta.search.AbstractSearchListener;
import net.jxta.myjxta.search.SearchListener;
import net.jxta.myjxta.search.SearchModifier;
import net.jxta.myjxta.search.Searcher;
import net.jxta.myjxta.ui.model.SearchTableModel;
import net.jxta.myjxta.util.Group;
import net.jxta.myjxta.util.Peer;
import net.jxta.myjxta.util.Resources;
import net.jxta.myjxta.util.Share;
import net.jxta.myjxta.util.objectmodel.GroupNode;
import net.jxta.myjxta.util.objectmodel.JxtaNode;
import net.jxta.myjxta.util.objectmodel.PeerNode;
import net.jxta.myjxta.util.objectmodel.ShareNode;
import net.jxta.protocol.PeerGroupAdvertisement;
import net.jxta.protocol.PipeAdvertisement;
import net.jxta.share.ContentAdvertisement;

import javax.swing.*;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.Iterator;
import java.util.ResourceBundle;
import java.util.Timer;
import java.util.TimerTask;

/**
 * @author james todd [gonzo at jxta dot org]
 * @version $Id: SearchPanel.java,v 1.14 2006/06/06 20:04:08 nano Exp $
 */

public final class SearchPanel
        extends JPanel
        implements Dismissable {

//    private static final int SECOND = 1000;
//    private static final int MINUTE = SECOND * 60;
//    private static final int HOUR = MINUTE * 60;
//    private static final int MAX = 3 * MINUTE;
    private static final int INTERVAL = 250;
    private static final ResourceBundle STRINGS = Resources.getStrings();

    private View view = null;
    private Group group = null;
    private MyJXTA myjxta = null;
    private int context = -1;
    private ComboBoxModel modifierModel = null;
    private JComboBox modifier = null;
    private JTextField query = null;
    private final BoundedRangeModel progress = null;
    private Timer timer = null;
    private JToggleButton search = null;
//    private JButton dismiss = null;
    private Searcher searcher = null;
    private SearchTableModel results = null;

    public SearchPanel(View view, int context) {
        this.view = view;
        this.myjxta = this.view.getControl();
        this.context = context;

        Group g = this.view.getGroup();

        while (g != null &&
                ! g.isJoined()) {
            g = g.getParentGroup();
        }

        this.group = g;

        init();
    }

    public void setVisible(boolean isVisible) {
        if (isVisible) {
            checkState();
            query.requestFocus();
        }

        super.setVisible(isVisible);
    }

    public void dismiss() {
        stop();

        //this.view.removeSearch(this);
    }

    private void init() {
        this.results = new SearchTableModel(this.context);
        this.searcher = new Searcher(this.context, this.group, this.myjxta,
                SearchListenerFactory.create(this.context, this.results));
        this.timer = null;

        setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));

        add(createSearchPanel());
        //add(createProgressPanel());
        add(createResultsPanel());

        setName(this.searcher.getLabel());

        setSize(new Dimension(UIConstants.SEARCH_WIDTH,
                UIConstants.SEARCH_HEIGHT));
        setPreferredSize(getSize());
        setMinimumSize(getPreferredSize());

//        getRootPane().setDefaultButton(this.search);
    }

    private JPanel createSearchPanel() {
        JPanel p = new JPanel();

        p.setLayout(new BorderLayout());

        JPanel sp = new JPanel();

        sp.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
        sp.setLayout(new BoxLayout(sp, BoxLayout.X_AXIS));

        sp.add(this.modifier = createModifier());
        sp.add(Box.createHorizontalGlue());
        sp.add(Box.createHorizontalStrut(5));
        sp.add(this.query = createQuery());
        sp.add(Box.createHorizontalGlue());
        sp.add(Box.createHorizontalStrut(5));
        sp.add(this.search = createSearch());
        sp.add(Box.createRigidArea(new Dimension(5, 0)));
        sp.add(Box.createHorizontalGlue());

        p.add(BorderLayout.CENTER, sp);

        return p;
    }

//    private JPanel createProgressPanel() {
//        JPanel p = new JPanel();
//
//        p.setLayout(new BorderLayout());
//
//        JPanel pp = new JPanel();
//
//        pp.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
//        pp.setLayout(new BoxLayout(pp, BoxLayout.X_AXIS));
//
//        pp.add(createProgress());
//        pp.add(Box.createHorizontalGlue());
//        pp.add(Box.createHorizontalStrut(5));
//        pp.add(this.dismiss = createDismiss());
//
//        p.add(pp);
//
//        return p;
//    }

    private JPanel createResultsPanel() {
        JPanel p = new JPanel();

        p.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
        p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS));

        JScrollPane sp = new JScrollPane(createResults(),
                ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
                ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);

        p.add(sp);

        return p;
    }

    private JComboBox createModifier() {
        JComboBox cb = new JComboBox();

        cb.setModel(this.modifierModel = new DefaultComboBoxModel());
        cb.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent ae) {
                checkState();
            }
        });

//        SearchModifier modifier = null;

        for (Iterator m = this.searcher.getModifiers().iterator();
             m.hasNext();) {
            SearchModifier sm = (SearchModifier) m.next();

            ((DefaultComboBoxModel) this.modifierModel).addElement(sm);

            if (sm.getLabel().equals(SearchModifier.DEFAULT)) {
                this.modifierModel.setSelectedItem(sm);
            }
        }

        return cb;
    }

    private JTextField createQuery() {
        JTextField tf = new JTextField(10);

        tf.setAction(
                new AbstractAction(STRINGS.getString("label.search")) {
                    public void actionPerformed(ActionEvent ae) {
                        ButtonModel m = search.getModel();

                        m.setPressed(true);
                        m.setSelected(true);

                        search();
                    }
                }
        );
        tf.addKeyListener(new KeyListener() {
            public void keyTyped(KeyEvent ke) {
            }

            public void keyPressed(KeyEvent ke) {
            }

            public void keyReleased(KeyEvent ke) {
                checkState();
            }
        });

        return tf;
    }

    private JToggleButton createSearch() {
        JToggleButton tb = new JToggleButton(new AbstractAction() {
            public void actionPerformed(ActionEvent ae) {
                search();
            }
        });

        tb.setText(STRINGS.getString("action.start"));
        tb.addKeyListener(new AbstractButtonKeyListener(tb) {
            public void keyReleased(KeyEvent ke) {
                ButtonModel m = getButton().getModel();

                m.setPressed(! m.isPressed());
                m.setSelected(true);

                getButton().getAction().actionPerformed(null);
            }
        });

        return tb;
    }

//    private JProgressBar createProgress() {
//        this.progress = new DefaultBoundedRangeModel(0, MAX, 0, MAX);
//
//        JProgressBar pb =
//            new JProgressBar(this.progress) {
//                final int SECOND = 1000;
//                final int MINUTE = SECOND * 60;
//                final int HOUR = MINUTE * 60;
//
//                //public Dimension getPreferredSize() {
//                //    return new Dimension(150,
//                //                         super.getPreferredSize().height);
//                //}
//
//                public String getString() {
//                    int r = MAX - progress.getValue();
//                    int h = r / HOUR;
//                    int m = (r - h * HOUR) / MINUTE;
//                    int s = (r - h * HOUR - m * MINUTE) / SECOND;
//
//                    return (h < 10 ? "0" : "") + h + ":" +
//                           (m < 10 ? "0" : "") + m + ":" +
//                           (s < 10 ? "0" : "") + s;
//                }
//            };
//
//        pb.setStringPainted(true);
//
//        return pb;
//    }

//remove!
//    private JButton createDismiss() {
//        JButton b = new JButton(new AbstractAction() {
//            public void actionPerformed(ActionEvent ae) {
//                dismiss();
//            }
//        });
//
//        b.setText(STRINGS.getString("action.dismiss"));
//        b.addKeyListener(new AbstractButtonKeyListener(b) {
//             public void keyReleased(KeyEvent ke) {
//                 getButton().getAction().actionPerformed(null);
//             }
//         });
//
//        return b;
//    }

    private JTable createResults() {
        JTable t = new JTable();

        t.setShowHorizontalLines(false);
        t.setShowVerticalLines(false);
        t.setCellSelectionEnabled(false);
        t.setColumnSelectionAllowed(false);
        t.setRowSelectionAllowed(false);
        t.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);

        if (this.results != null) {
            this.results.addTableModelListener(new TableModelListener() {
                public void tableChanged(TableModelEvent tme) {
                    JxtaNode jn = null;
                    JxtaNode pn = null;
                    Object o = null;

                    for (int i = tme.getFirstRow(); i < tme.getLastRow() + 1; i++) {
                        o = results.getData(tme.getFirstRow());

                        // xxx: generalize
                        if (o != null) {
                            if (o instanceof Group) {
                                Group g = (Group) o;
                                jn = new GroupNode(g);
                                pn = new GroupNode(g.getParentGroup());
                            } else if (o instanceof Peer) {
                                jn = new PeerNode((Peer) o, group);
                                pn = new GroupNode(group);
                            } else if (o instanceof Share) {
                                jn = new ShareNode((Share) o, group, ShareNode.REMOTE_SHARE);
                                pn = new GroupNode(group);
                            }
                        }

                        if (jn != null) {
                            jn.setParent(pn);
                            myjxta.addJxtaNode(jn);

                        }
                    }
                }
            });

            t.setModel(this.results);
        }

        return t;
    }

    private void checkState() {
        if (this.modifier != null &&
                this.query != null &&
                this.search != null) {
            boolean isEquality =
                    ((SearchModifier) this.modifierModel.getSelectedItem()).isEquality();

            this.search.setEnabled(! isEquality ||
                    (isEquality &&
                            this.query.getText().trim().length() > 0));
        }
    }

    private void search() {
        if (this.search.getModel().isSelected()) {
            start();
        } else {
            stop();
        }
    }

    private String getTerm() {
        return ((SearchModifier) this.modifierModel.getSelectedItem()).
                createTerm(this.query.getText());
    }

    private void start() {
        startUI();
        startSearch();
        startTimer();
    }

    private void stop() {
        stopTimer();
        stopSearch();
        stopUI();
    }

    private void startUI() {
        for (int i = 0; i < this.results.getRowCount(); i++) {
            this.results.removeRow(i);
        }

        this.search.setText(STRINGS.getString("action.stop"));
        this.modifier.setEnabled(false);
        this.query.setEnabled(false);


        if (this.getParent() instanceof JTabbedPane) {
            JTabbedPane jTabbedPane = ((JTabbedPane) this.getParent());
            int i = jTabbedPane.indexOfComponent(this);
            jTabbedPane.setTitleAt(i, this.searcher.getLabel() + ": " + getTerm());
        } else {
            throw new IllegalStateException("Search panel is not located inside a tabbed panel?");
        }

    }

    private void startSearch() {
        this.searcher.start(getTerm());
        this.view.setStatus(STRINGS.getString("status.search.start") +
                " " + this.searcher.getLabel() + " is " + getTerm());

    }

    private void stopUI() {
        this.search.setText(STRINGS.getString("action.start"));
        this.modifier.setEnabled(true);
        this.query.setEnabled(true);
    }

    private void stopSearch() {
        this.searcher.stop();
        this.view.setStatus(STRINGS.getString("status.search.stop") +
                " " + this.searcher.getLabel() + " is " + getTerm());
    }

    private void startTimer() {
        if (this.timer == null &&
                this.progress != null) {
            if (this.progress.getValue() ==
                    this.progress.getMaximum()) {
                this.progress.setValue(0);
            }

            this.timer = new Timer();

            this.timer.schedule(createTimerTask(), INTERVAL, INTERVAL);
        }
    }

    private TimerTask createTimerTask() {
        return new TimerTask() {
            public void run() {
                if (progress.getValue() <
                        progress.getMaximum()) {
                    updateProgress();
                } else {
                    progress.setValue(progress.getMaximum());
                    ((JToggleButton.ToggleButtonModel)
                            search.getModel()).setSelected(false);
                    search.getAction().actionPerformed(null);
                }
            }
        };
    }

    private void updateProgress() {
        if (this.progress != null) {
            this.progress.setValue(this.progress.getValue() + INTERVAL);
        }
    }

    private void stopTimer() {
        if (this.timer != null) {
            this.timer.cancel();

            this.timer = null;

            this.search.setText(STRINGS.getString("action.start"));
        }
    }
}

class SearchListenerFactory {

    public static SearchListener create(final int context,
                                        final SearchTableModel results) {
        SearchListener listener = null;

        switch (context) {
            case Searcher.GROUP:
                listener = new AbstractSearchTableSearchListener() {
                    public void add(JxtaNode node) {
                        Group g = ((GroupNode) node).getGroup();
                        PeerGroupAdvertisement pga =
                                g.getPeerGroupAdvertisement();

                        if (! inCache(pga.getID().toString())) {
                            Object[] row = new Object[]{pga.getName(),
                                    pga.getDescription()};

                            process(results, context, g, row);
                        }
                    }
                };

                break;

            case Searcher.PIPE:
                listener = new AbstractSearchTableSearchListener() {
                    public void add(JxtaNode node) {
                        Peer p = ((PeerNode) node).getPeer();
                        PipeAdvertisement pa = p.getPipeAdvertisement();

                        if (! inCache(pa.getID().toString())) {
                            Object[] row = new Object[]{p.getName()};

                            process(results, context, p, row);
                        }
                    }
                };

                break;

            case Searcher.SHARE:
                listener = new AbstractSearchTableSearchListener() {
                    public void add(JxtaNode node) {
                        Share s = ((ShareNode) node).getShare();
                        ContentAdvertisement ca = s.getContentAdvertisement();

                        if (ca != null &&
                                ! inCache(ca.getContentId().toString())) {
                            Object[] row = new Object[]{ca.getName(), ca.getType(),
                                    new Long(ca.getLength()).toString(), ca.getDescription()};

                            process(results, context, s, row);
                        }
                    }
                };

                break;
        }

        return listener;
    }
}

// xxx: generalize

abstract class AbstractSearchTableSearchListener
        extends AbstractSearchListener {

    protected void process(final SearchTableModel results,
                           final int context, final Object o, final Object[] row) {
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                results.addRow(context, o, row);
            }
        });
    }
}
